package com.devtools.project.tool.datasource.service;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.devtools.common.constant.Constants;
import com.devtools.common.exception.base.BaseException;
import com.devtools.common.utils.StringUtils;
import com.devtools.project.tool.datasource.domain.DataTable;
import com.devtools.project.tool.datasource.domain.DataTableColumn;
import com.devtools.project.tool.datasource.domain.DataTableQuery;
import com.devtools.project.tool.datasource.domain.ToolDatasource;
import com.devtools.project.tool.datasource.mapper.DatasourceMapper;
import com.devtools.project.tool.gen.domain.ColumnInfo;
import com.devtools.project.tool.gen.domain.GenConfigVo;
import com.devtools.project.tool.gen.domain.TableInfo;
import com.devtools.project.tool.gen.util.GenUtils;
import com.devtools.project.tool.gen.util.VelocityInitializer;
import com.devtools.project.tool.module.domain.Module;
import com.devtools.project.tool.module.service.IModuleService;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 数据源 服务层实现
 *
 * @author devtools
 * @date 2022-11-28
 */
@Service
public class DatasourceServiceImpl extends ServiceImpl<DatasourceMapper, ToolDatasource> implements IDatasourceService {
    @Autowired
    private IModuleService moduleService;

    /**
     * 预览数据
     *
     * @param query
     * @return
     */
    @Override
    public JSONObject wordData(DataTableQuery query) {
        JSONObject result = new JSONObject();
        ToolDatasource datasource = super.getById(query.getDatasourceId());
        result.put("datasource", datasource);
        if (datasource != null) {
            try {
                query.setShowColumn(true);
                //获取表集合
                List<DataTable> tableList = getDataTableList(query);
                List<Module> moduleList = getModuleList(tableList, query.getDatasourceId());
                result.put("tableList", tableList);
                result.put("template", CollectionUtils.isEmpty(moduleList) ? "tool/datasource/word" : "tool/datasource/word2");
                result.put("moduleList", moduleList);
                result.put("downloadUrl", getDownloadUrl(query));
                //设置数据库接口文档导出名称
                result.put("docName", datasource.getDatasourceName() + "数据库设计文档");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 获取下载地址
     *
     * @param query
     * @return
     */
    private String getDownloadUrl(DataTableQuery query) {
        StringBuilder download = new StringBuilder("/tool/datasource/downloadWord?datasourceId=" + query.getDatasourceId());
        if (StringUtils.isNotEmpty(query.getTablePrefix())) {
            download.append("&tablePrefix=").append(query.getTablePrefix());
        }
        if (StringUtils.isNotEmpty(query.getTables())) {
            download.append("&tables=").append(query.getTables());
        }
        return download.toString();
    }

    /**
     * 获取表列表  TODO 后期根据驱动扩展其他类型的数据库
     *
     * @return
     */
    @SneakyThrows
    public List<DataTable> getDataTableList(DataTableQuery query) {
        //获取数据源
        ToolDatasource datasource = super.getById(query.getDatasourceId());
        //构建JdbcTemplate
        DruidDataSource druidDataSource = getDruidDataSource(datasource);
        try {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(druidDataSource);
            //构建查询条件
            String tableSql = getQueryTableSql(query);
            //查看表列表
            List<DataTable> tableList = jdbcTemplate.query(tableSql, new BeanPropertyRowMapper<>(DataTable.class), datasource.getTableSchema());

            if (query.getShowColumn()) {
                //填充表字段
                List<DataTableColumn> columnList = getDataTableColumnList(jdbcTemplate, datasource);
                Map<String, List<DataTableColumn>> tableColumnMap = columnList.stream().collect(Collectors.groupingBy(DataTableColumn::getTableName));
                for (DataTable table : tableList) {
                    List<DataTableColumn> list = tableColumnMap.get(table.getTableName());
                    list.sort(Comparator.comparingInt(DataTableColumn::getOrdinalPosition));
                    table.setColumnList(list);
                    table.setTableSchema(datasource.getTableSchema());
                }
            }
            return tableList;
        } finally {
            druidDataSource.close();
        }
    }

    /**
     * 构建查询条件
     *
     * @param query
     * @return
     */
    private String getQueryTableSql(DataTableQuery query) {
        String tableSql = "select TABLE_NAME,TABLE_COMMENT,CREATE_TIME,UPDATE_TIME from INFORMATION_SCHEMA.Tables where table_schema = ?";
        if (StringUtils.isNotEmpty(query.getTables())) {
            tableSql = tableSql + " and TABLE_NAME in ('" + query.getTables().replaceAll(",", "','") + "')";
        }
        if (StringUtils.isNotEmpty(query.getTableName())) {
            tableSql = tableSql + " and TABLE_NAME like '%" + query.getTableName() + "%'";
        }
        if (StringUtils.isNotEmpty(query.getTableName())) {
            tableSql = tableSql + " and TABLE_COMMENT like '%" + query.getTableComment() + "%'";
        }
        if (StringUtils.isNotEmpty(query.getTablePrefix()) && !"null".equalsIgnoreCase(query.getTablePrefix())) {
            tableSql = tableSql + " and TABLE_NAME like '" + query.getTablePrefix() + "%'";
        }
        return tableSql;
    }

    /**
     * 生成代码
     *
     * @param vo
     * @return
     * @throws Exception
     */
    @Override
    public byte[] generatorCode(GenConfigVo vo) {
        //获取表集合
        DataTableQuery query = new DataTableQuery();
        BeanUtils.copyProperties(vo, query);
        query.setTablePrefix(null);
        query.setShowColumn(true);
        List<DataTable> tableList = getDataTableList(query);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        for (DataTable dataTable : tableList) {
            //数据转换
            TableInfo table = new TableInfo();
            BeanUtils.copyProperties(dataTable, table);
            // 查询列信息
            List<ColumnInfo> columns = new ArrayList<>();
            for (DataTableColumn dataTableColumn : dataTable.getColumnList()) {
                ColumnInfo columnInfo = new ColumnInfo();
                BeanUtils.copyProperties(dataTableColumn, columnInfo);
                columns.add(columnInfo);
            }
            // 生成代码
            generatorCode(vo, table, columns, zip);
        }
        IOUtils.closeQuietly(zip);

        return outputStream.toByteArray();
    }

    /**
     * 获取model list
     *
     * @param tableList
     * @param datasourceId
     * @return
     */
    public List<Module> getModuleList(List<DataTable> tableList, Integer datasourceId) {
        QueryWrapper<Module> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Module::getDatasourceId, datasourceId);
        queryWrapper.lambda().orderByAsc(Module::getModuleSort);
        List<Module> moduleList = moduleService.list(queryWrapper);
        //没有配置模块，直接返回空
        if (CollectionUtils.isEmpty(moduleList)) {
            return new ArrayList<>();
        }
        Map<String, DataTable> dataTableMap = tableList.stream().collect(Collectors.toMap(DataTable::getTableName, o -> o));
        for (Module module : moduleList) {
            Pattern pattern = Pattern.compile(module.getTablePrefix() + "*");
            for (DataTable table : tableList) {
                if (pattern.matcher(table.getTableName()).find()) {
                    module.getTableList().add(table);
                    dataTableMap.remove(table.getTableName());
                }
            }
        }
        //将没有匹配到得表放在其他模块中
        if (!dataTableMap.isEmpty()) {
            Module module = new Module();
            module.setTablePrefix("*");
            module.setModuleName("其他");
            List<DataTable> otherList = new ArrayList<>();
            otherList.addAll(dataTableMap.values());
            module.setTableList(otherList);
            moduleList.add(module);
        }
        return moduleList;

    }

    /**
     * 获取表列表  TODO 后期根据驱动扩展其他类型的数据库
     *
     * @param jdbcTemplate
     * @param datasource
     * @return
     */
    public List<DataTableColumn> getDataTableColumnList(JdbcTemplate jdbcTemplate, ToolDatasource datasource) {
        String sql = "select c.table_name tableName,data_type,extra, " +
                "column_name,c.column_type ,c.column_comment , c.is_nullable isNullable " +
                ", c.column_default columnDefault , ordinal_position ordinalPosition ,column_key columnKey " +
                "from " +
                "information_schema.`columns` c " +
                "where  table_schema = ? ";
        List<DataTableColumn> columnList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(DataTableColumn.class), datasource.getTableSchema());
        return columnList;
    }

    /**
     * 获取数据源
     *
     * @param datasource
     * @return
     * @throws Exception
     */
    public DruidDataSource getDruidDataSource(ToolDatasource datasource) {
        String url = "jdbc:mysql://" + datasource.getDatasourceIp() + ":" + datasource.getDatasourcePort() + "/" + datasource.getTableSchema() + "?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8";
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(datasource.getUsername());
        druidDataSource.setPassword(datasource.getPassword());
        druidDataSource.setConnectionErrorRetryAttempts(0);
        druidDataSource.setMaxActive(1);
        druidDataSource.setMaxWaitThreadCount(1);
        druidDataSource.setBreakAfterAcquireFailure(true);
        //可选设置
        druidDataSource.setInitialSize(1);
        druidDataSource.setMaxWait(2000);
        return druidDataSource;

    }

    /**
     * 生成代码
     */
    public void generatorCode(GenConfigVo configVo, TableInfo table, List<ColumnInfo> columns, ZipOutputStream zip) {
        // 表名转换成Java属性名
        String className = GenUtils.tableToJava(configVo, table.getTableName());
        table.setClassName(className);
        table.setClassname(com.devtools.common.utils.StringUtils.uncapitalize(className));
        // 列信息
        table.setColumns(GenUtils.transColums(columns));
        // 设置主键
        table.setPrimaryKey(table.getColumnsLast());
        VelocityInitializer.initVelocity();
        VelocityContext context = GenUtils.getVelocityContext(configVo, table);
        // 获取模板列表
        List<String> templates = GenUtils.getTemplates();
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
            tpl.merge(context, sw);
            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(GenUtils.getFileName(configVo, template, table)));
                IOUtils.write(sw.toString(), zip, Constants.UTF8);
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException e) {
                throw new BaseException("渲染模板失败，表名：" + table.getTableName(), e.getMessage());
            }
        }
    }
}